//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#include "Platforms/SDL/SDLPlatform.h"

#if defined( PLATFORM_SDL )

namespace Engine
{
namespace Platforms
{

	SDLPlatform *	SDLPlatform::_instance = null;
	
/*
=================================================
	constructor
=================================================
*/
	SDLPlatform::SDLPlatform () :
		_pause(NO_FOCUS), _isLooping(false), _backgroundMode(false),
		_window(null), _context(null), _requestQuit(false), _requiredDeviceType( EDevice::AUTO )
	{
		_timer.Start();
		
		_directories[ EDirectory::INTERNAL ]			= "internal";
		_directories[ EDirectory::INTERNAL_CACHE ]		= "internal_cache";
		_directories[ EDirectory::EXTERNAL ]			= "external";
		_directories[ EDirectory::EXTERNAL_CACHE ]		= "external_cache";
		_directories[ EDirectory::EXTERNAL_STORAGE ]	= "sdcard";
	}

/*
=================================================
	destructor
=================================================
*/
	SDLPlatform::~SDLPlatform ()
	{
	}
	
/*
=================================================
	SendEvent
=================================================
*/
	void SDLPlatform::SendEvent (const SysEvent &ev)
	{
		_application->GetSubSystems()->GetEventSystem()->Send( ev );
	}
	
/*
=================================================
	SendEventDelayed
=================================================
*/
	void SDLPlatform::_SendEventDelayed (EventSystem *es, const SysEvent &ev)
	{
		es->Send( ev );
	}

	void SDLPlatform::SendEventDelayed (const SysEvent &ev)
	{
		_application->Push(
			ParallelOp::FunctionCall( FunctionBuilder< void (*)(EventSystem *, const SysEvent &) >::Create(
				&_SendEventDelayed, _application->GetSubSystems()->GetEventSystem(), ev ) ) );
	}

/*
=================================================
	Loop
=================================================
*/
	int SDLPlatform::Loop ()
	{
		CHECK_ERR( _Init(),		  -1 );
		
		_EnterForeground( false );
		CHECK_ERR( _CreateWindow(),  -2 );
		_EnterForeground( true );

		CHECK_ERR( _EventLoop(),     -3 );

		_EnterBackground( false );

		// On Destroy Surface
		if ( _window != null and _context != null )
		{
			TSysEvent< ESystemEvent::APPLICATION >	ev;

			ev.app.type	= SysEvent::Application::SURFACE_DESTROYED;
			SendEvent( ev );
	
			gles::GLES_Delete();
		}

		_Destroy();
		_EnterBackground( true );
		EnableAudio( false );

		// TODO: work on background

		if ( _backgroundMode )
		{
			_BackgroundLoop();
		}
		
		if ( _application.IsNotNull() )
		{
			TSysEvent< ESystemEvent::APPLICATION >	ev;

			ev.app.type = SysEvent::Application::DESTROY;
			SendEvent( ev );

			_application->Destroy();
			_application = null;
		}

		return 0;
	}
	
/*
=================================================
	InitRender
=================================================
*/
	bool SDLPlatform::InitRender (INOUT VideoSettings &vs)
	{
		CHECK_ERR( _InitOpenGL( vs ) );
		
		// Initialize OpenGL //
		CHECK_ERR( ::SDL_GL_MakeCurrent( _window, _context ) == 0 );
		CHECK_ERR( ::SDL_GL_SetSwapInterval( 1 ) == 0 );
		
		{
			using namespace gles;

			bool gles_initialized = GLES_Init();

			if ( gles_initialized )
			{
				string	gpu;

				const char	*	vendor		= null,
							*	renderer	= null,
							*	version		= null,
							*	sl_version	= null;

				GL_CALL( vendor		= (const char *) glGetString( GL_VENDOR ) );
				GL_CALL( renderer	= (const char *) glGetString( GL_RENDERER ) );
				GL_CALL( version	= (const char *) glGetString( GL_VERSION ) );
				GL_CALL( sl_version	= (const char *) glGetString( GL_SHADING_LANGUAGE_VERSION ) );
				
				gpu << vendor << ' ' << renderer << ", ver: " << version << ", SL ver: " << sl_version;
				_device.SetGPUName( gpu );
				_device.WriteToLog();
			}
		}

		TSysEvent< ESystemEvent::APPLICATION >	ev;

		ev.app.type	= SysEvent::Application::SURFACE_CREATED;

		SendEventDelayed( ev );

		return true;
	}
	
/*
=================================================
	SwapBuffers
=================================================
*/
	void SDLPlatform::SwapBuffers ()
	{
		const bool	has_rc = ( _window != null and _context != null );

		if ( has_rc )
		{
			::SDL_GL_SwapWindow( _window );
		}
	}
	
/*
=================================================
	IsRenderingAvailable
=================================================
*/
	bool SDLPlatform::IsRenderingAvailable ()
	{
		return	_window != null and
				_context != null and;
	}

/*
=================================================
	GetWidget
=================================================
*/
	Ptr< IWidget >  SDLPlatform::GetWidget (IWidget::EType type)
	{
		/*switch ( type )
		{
			case IWidget::EDIT_TEXT :
				return &_editText;

			case IWidget::WEB_VIEW :
				return &_webView;
		}*/

		return null;
	}
		
/*
=================================================
	EnableAudio
=================================================
*/
	void SDLPlatform::EnableAudio (bool enabled)
	{
		TSysEvent< ESystemEvent::AUDIO >	se;

		se.audio.type	= enabled ? SysEvent::Audio::SET_FOCUS : SysEvent::Audio::KILL_FOCUS;

		SendEvent( se );
	}
	
/*
=================================================
	CheckLicense
=================================================
*/
	void SDLPlatform::CheckLicense (OnLicenseChecked_t callback, uni_c_string publicKey)
	{
		TODO( "CheckLicense" );

		callback( ELicenseCheckResponse::LICENSED, "" );
	}

/*
=================================================
	Terminate
=================================================
*/
	void SDLPlatform::Terminate ()
	{
		if ( _isLooping )
		{
			_pause |= ON_DESTROY;
			_requestQuit = true;
		}

		_backgroundMode = false;
	}

/*
=================================================
	InitDevice
=================================================
*/
	void SDLPlatform::InitDevice (EDevice::type deviceType)
	{
		if ( deviceType != EDevice::AUTO )
		{
			DeviceEmulator::Create( _device, deviceType );

			// if window created //
			if ( _window != null )
			{
			}
			else
			if ( IsNotZero( _windowSize ) )
			{
				_device.GetDisplayRef().SetResolution( _windowSize );
			}
			else
			{
				_windowSize = _device.GetDisplay().Resolution();
			}
		}
		
		_requiredDeviceType = deviceType;
		_device.WriteToLog();
	}
	
/*
=================================================
	SetDisplayOrientation
=================================================
*/
	void SDLPlatform::SetDisplayOrientation (EDisplayOrientation::type orientation)
	{
		TSysEvent< ESystemEvent::WINDOW >	se;

		se.window.type = SysEvent::Window::ORIENTATION_CHANGED;

		switch ( orientation )
		{
			case EDisplayOrientation::DEFAULT :
			case EDisplayOrientation::SENSOR :
			case EDisplayOrientation::PORTRAIT :
				se.window.orientation = Display::ORIENT_0;
				break;

			case EDisplayOrientation::PORTRAIT_REVERSE :
				se.window.orientation = Display::ORIENT_180;
				break;

			case EDisplayOrientation::LANDSCAPE :
				se.window.orientation = Display::ORIENT_90;
				break;

			case EDisplayOrientation::LANDSCAPE_REVERSE :
				se.window.orientation = Display::ORIENT_270;
				break;
		}

		_device.GetDisplayRef().SetOrientation( se.window.orientation );

		SendEvent( se );
	}

/*
=================================================
	BackgroundMode
=================================================
*/
	void SDLPlatform::BackgroundMode (bool enabled)
	{
		_backgroundMode = enabled;
	}

/*
=================================================
	SendEmail
=================================================
*/
	void SDLPlatform::SendEmail (uni_c_string address, uni_c_string subject,
								 uni_c_string text, uni_c_string attachment)
	{
		TODO( "SendEmail" );
	}
	
/*
=================================================
	SendSMS
=================================================
*/
	void SDLPlatform::SendSMS (uni_c_string number, uni_c_string text)
	{
		TODO( "SendSMS" );
	}
	
/*
=================================================
	PhoneCall
=================================================
*/
	void SDLPlatform::PhoneCall (uni_c_string number)
	{
		TODO( "PhoneCall" );
	}
	
/*
=================================================
	OpenURL
=================================================
*/
	void SDLPlatform::OpenURL (uni_c_string url)
	{
		TPlatformUtils::OpenURL( url );
	}

/*
=================================================
	ShowNotification
=================================================
*/
	void SDLPlatform::ShowNotification (uni_c_string text, bool longTime)
	{
		TODO( "ShowNotification" );
	}
		
/*
=================================================
	ShowMessage
=================================================
*/
	void SDLPlatform::ShowMessage (const BaseObjectPtr &obj,
										void (* callback) (const BaseObjectPtr &obj, int result),
										uni_c_string caption, uni_c_string text,
										uni_c_string positive, uni_c_string negative)
	{
		TODO( "ShowMessage" );
	}
	
/*
=================================================
	GetEventSystem
=================================================
*/
	EventSystemPtr SDLPlatform::GetEventSystem ()
	{
		if ( _application.IsNull() )
			return null;

		return _application->GetSubSystems()->GetEventSystem().ptr();
	}
	
/*
=================================================
	GetThread
=================================================
*/
	IParallelThreadPtr SDLPlatform::GetThread ()
	{
		if ( _application.IsNull() )
			return null;

		return _application->GetSubSystems()->GetCurrentThread().ptr();
	}

/*
=================================================
	CreateInstance
=================================================
*/
	void SDLPlatform::CreateInstance ()
	{
		if ( _instance == null )
		{
			_instance = new SDLPlatform();
		}
	}
	
/*
=================================================
	DestroyInstance
=================================================
*/
	void SDLPlatform::DestroyInstance ()
	{
		if ( _instance != null )
		{
			delete _instance;
			_instance = null;
		}
	}
	
/*
=================================================
	_GetScreenResolution
=================================================
*/
	uvec2 SDLPlatform::_GetScreenResolution ()
	{
		::SDL_DisplayMode mode = {0};
		::SDL_GetCurrentDisplayMode( 0, &mode );
		return uvec2( mode.w, mode.h );
	}

/*
=================================================
	_CreateWindow
=================================================
*/
	bool SDLPlatform::_CreateWindow ()
	{
		Display const &	disp = _device.GetDisplay();

		CHECK_ERR( ::SDL_VideoInit( null ) == 0 );

		uint	flags = int(SDL_WINDOW_OPENGL) | int(SDL_WINDOW_HIDDEN) | int(SDL_WINDOW_RESIZABLE);

		int width	= disp.Resolution().x;
		int height	= disp.Resolution().y;
		int x		= 0;
		int y		= 0;
		
		::SDL_DisplayMode mode = {0};
		::SDL_GetCurrentDisplayMode( 0, &mode );

		// align to center
		{
			x = Max( (mode.w - width) / 2, 0 );
			y = Max( (mode.h - height) / 2, 0 );
		}

		CHECK_ERR( (_window = ::SDL_CreateWindow( ENGINE_NAME, x, y, width, height, flags )) != null );
		
		_windowSize = ivec2( width, height );
		_windowPos  = ivec2( x, y );
		return true;
	}

/*
=================================================
	_InitOpenGL
=================================================
*/
	bool SDLPlatform::_InitOpenGL (INOUT VideoSettings &vs)
	{
		CHECK_VALUE_RT( _window != null, "Window must be created before init OpenGL!", false );

#		ifdef MOBILE_EMULATOR
			vs.colorFormat = VideoSettings::R8_G8_B8_A8;

			::SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION,	4 );
			::SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION,	2 );
			::SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK,		SDL_GL_CONTEXT_PROFILE_COMPATIBILITY );
#		else
		
			::SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION,	2 );
			::SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION,	0 );
			::SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK,		SDL_GL_CONTEXT_PROFILE_ES );
			::SDL_GL_SetAttribute( SDL_GL_CONTEXT_EGL,				1 );
#		endif

		::SDL_GL_SetAttribute( SDL_GL_RED_SIZE,					vs.RedBits() );
		::SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE,				vs.GreenBits() );
		::SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE,				vs.BlueBits() );
		::SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE,				vs.AlphaBits() );
		::SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER,				1 );
		::SDL_GL_SetAttribute( SDL_GL_BUFFER_SIZE,				0 );
		::SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE,				vs.depthBits );
		::SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE,				0 );
		::SDL_GL_SetAttribute( SDL_GL_ACCELERATED_VISUAL,		1 );

		CHECK_ERR( (_context = ::SDL_GL_CreateContext( _window )) != null );
		//CHECK_ERR( ::SDL_GL_MakeCurrent( _window, _context ) == 0 );

		return true;
	}

/*
=================================================
	_Destroy
=================================================
*/
	void SDLPlatform::_Destroy ()
	{
		// TODO: save state
		// _application->SaveState( data );
		
		CHECK( ::SDL_GL_MakeCurrent( _window, null ) == 0 );

		if ( _context != null )
		{
			::SDL_GL_DeleteContext( _context );
			_context = null;
		}

		::SDL_VideoQuit();
		
		if ( _window != null )
		{
			::SDL_DestroyWindow( _window );
			_window = null;
		}
		
		::SDL_Quit();
	}
	
/*
=================================================
	_Init
=================================================
*/
	bool SDLPlatform::_Init ()
	{
		CHECK_ERR( g_CreateMainApplication != null );
		g_CreateMainApplication( this, _application.ref() );

		CHECK_ERR( _application.IsNotNull() );

		// TODO
		//_editText.GetSubSystems().Override( _application->GetSubSystems() );
		//_webView.GetSubSystems().Override( _application->GetSubSystems() );

		TSysEvent< ESystemEvent::APPLICATION >	ev;

		ev.app.type = SysEvent::Application::CREATED;
		SendEvent( ev );

		return true;
	}
	
/*
=================================================
	_Quit
=================================================
*/
	void SDLPlatform::_Quit ()
	{
	}
	
/*
=================================================
	_EnterForeground
=================================================
*/
	void SDLPlatform::_EnterForeground (bool completed)
	{
		TSysEvent< ESystemEvent::APPLICATION >	ev;

		ev.app.type		 = SysEvent::Application::ENTER_FOREGROUND;
		ev.app.completed = completed;
		
		SendEvent( ev );
	}
	
/*
=================================================
	_EnterBackground
=================================================
*/
	void SDLPlatform::_EnterBackground (bool completed)
	{
		TSysEvent< ESystemEvent::APPLICATION >	ev;

		ev.app.type		 = SysEvent::Application::ENTER_BACKGROUND;
		ev.app.completed = completed;
		
		SendEvent( ev );
	}

/*
=================================================
	_VisibilityChanged
=================================================
*/
	void SDLPlatform::_VisibilityChanged (bool isVisible)
	{
		TSysEvent< ESystemEvent::WINDOW >	ev;

		ev.window.type = isVisible ? SysEvent::Window::SHOWN : SysEvent::Window::HIDDEN;

		SendEvent( ev );
	}
	
/*
=================================================
	_OnResized
=================================================
*/
	void SDLPlatform::_OnResized (uint w, uint h)
	{
		_windowSize.x = w;
		_windowSize.y = h;

		_device.GetDisplayRef().SetResolution( _windowSize );

		TSysEvent< ESystemEvent::WINDOW >	se;

		se.window.type		= SysEvent::Window::RESIZED;
		se.window.width		= _windowSize.x;
		se.window.height	= _windowSize.y;

		SendEvent( se );
	}
	
/*
=================================================
	_OnKey
=================================================
*/
	inline void SDLPlatform::_OnKey (int keyCode, bool down)
	{
		TSysEvent< ESystemEvent::RAW_KEY >	se( _GetTimestamp() );

		se.rawKey.code		= keyCode;
		se.rawKey.pressed	= down;

		SendEvent( se );
	}
	
/*
=================================================
	_OnTouch
=================================================
*/
	inline void SDLPlatform::_OnTouch (int id, bool down, int x, int y)
	{
		TSysEvent< ESystemEvent::RAW_TOUCH >	se( _GetTimestamp() );

		se.rawTouch.id		 = id;
		se.rawTouch.x		 = float( x );
		se.rawTouch.y		 = float( _windowSize.y - y );
		se.rawTouch.pressure = down ? 1.0f : 0.0f;
		se.rawTouch.motion	 = false;
		se.rawTouch.pressed	 = down;
		
		SendEvent( se );
	}
	
/*
=================================================
	_OnTouchMotion
=================================================
*/
	inline void SDLPlatform::_OnTouchMotion (int id, int x, int y)
	{
		TSysEvent< ESystemEvent::RAW_TOUCH >	se( _GetTimestamp() );

		se.rawTouch.id		 = id;
		se.rawTouch.x		 = float( x );
		se.rawTouch.y		 = float( _windowSize.y - y );
		se.rawTouch.pressure = 1.0f;
		se.rawTouch.motion	 = true;
		se.rawTouch.pressed	 = true;
		
		SendEvent( se );
	}

/*
=================================================
	_EventLoop
=================================================
*/
	bool SDLPlatform::_EventLoop ()
	{
		_isLooping = true;
		
		::SDL_ShowWindow( _window );
		_OnResized( _windowSize.x, _windowSize.y );

		while ( _isLooping )
		{
			_ProcessEvents();
			
			_Update( false );
		}
		return true;
	}
	
	
/*
=================================================
	_ProcessEvents
=================================================
*/
	void SDLPlatform::_ProcessEvents ()
	{
		SDL_Event	ev;

		while ( SDL_PollEvent( &ev ) )
		{
			switch ( ev.type )
			{
				// touch screen //
				case SDL_FINGERMOTION :
				{
					TODO( "check" );
					_OnTouchMotion( (int)ev.tfinger.fingerId, (int)ev.tfinger.x, (int)ev.tfinger.y );
					break;
				}

				case SDL_FINGERDOWN :
				case SDL_FINGERUP :
				{
					TODO( "check" );
					_OnTouch( (int)ev.tfinger.fingerId, ev.tfinger.type == SDL_FINGERDOWN, (int)ev.tfinger.x, (int)ev.tfinger.y );
					break;
				}


				// key //
				case SDL_KEYDOWN :
				case SDL_KEYUP :
				{
					_OnKey( ev.key.keysym.scancode, ev.type == SDL_KEYDOWN );
					break;
				}


				// quit //
				case SDL_QUIT :
				case SDL_APP_TERMINATING :
				{
					_isLooping = false;
					break;
				}


				// enter background //
				case SDL_APP_WILLENTERBACKGROUND :
					_EnterBackground( false );
					break;

				case SDL_APP_DIDENTERBACKGROUND :
					_EnterBackground( true );
					break;


				// enter foreground //
				case SDL_APP_WILLENTERFOREGROUND :
					_EnterForeground( false );
					break;

				case SDL_APP_DIDENTERFOREGROUND :
					_EnterForeground( true );
					break;


				// window event //
				case SDL_WINDOWEVENT :
				{
					switch ( ev.window.event )
					{
						// show //
						case SDL_WINDOWEVENT_SHOWN :
							_VisibilityChanged( true );
							break;

						// hide //
						case SDL_WINDOWEVENT_HIDDEN :
							_VisibilityChanged( false );
							break;

						// resize //
						case SDL_WINDOWEVENT_RESIZED :
							_OnResized( ev.window.data1, ev.window.data2 );
							break;

						// move //
						case SDL_WINDOWEVENT_MOVED :
							_OnMove( ivec2( ev.window.data1, ev.window.data2 ) );
							break;
					}
					break;
				}

				#if defined( MOBILE_EMULATOR )
					// mouse //
					case SDL_MOUSEMOTION :
					{
						if ( ev.motion.state != SDL_PRESSED )
							break;

						_OnTouchMotion( 0, ev.motion.x, ev.motion.y );
						break;
					}

					// mouse button //
					case SDL_MOUSEBUTTONDOWN :
					case SDL_MOUSEBUTTONUP :
					{
						_OnTouch( 0, ev.button.state == SDL_PRESSED, ev.button.x, ev.button.y );
						break;
					}
				#endif
			}
		}
	}
	
/*
=================================================
	_BackgroundLoop
=================================================
*/
	void SDLPlatform::_BackgroundLoop ()
	{
		while ( _backgroundMode )
		{
			_Update( false );
			Thread::Yield();
		}
	}
	
/*
=================================================
	_Update
=================================================
*/
	void SDLPlatform::_Update (bool redraw)
	{
		const bool	has_rc = ( _window != null and _context != null );

		if ( has_rc )
		{
			::SDL_GL_MakeCurrent( _window, _context );
		}

		_application->Flush();
		_application->ProcessMessages();

		TSysEvent< ESystemEvent::UPDATE >	ev( _GetTimestamp() );

		ev.update.timeDelta			= (float) (ev.timestamp - _lastUpdateTime).ToDouble().GetSec();
		ev.update.redraw			= redraw;
		ev.update.renderAvailable	= has_rc;

		_lastUpdateTime = ev.timestamp;
		
		SendEvent( ev );
	}

	
}	// Platforms
}	// Engine


/*
=================================================
	main
=================================================
*/
int main (int argc, char *argv[])
{
	using namespace Engine;
	using namespace Platforms;

	SDLPlatform::CreateInstance();
	int ret = SDLPlatform::GetInstance()->Loop();

	Referenced::s_ChenckNotReleasedObjects();

	return ret;
}


#endif	// PLATFORM_SDL
